#ifndef __POOL_SERVER_H__
#define __POLL_SERVER_H__

#include <poll.h>
#include <iostream>
#include "Log.hpp"
#include "Sock.hpp"

using namespace std;

#define FD_NONE -1 // FD_NONE表示该文件描述符不需要关心

class PollServer
{
private:
    static const int defaultnfds = 100;
    static const int defaulttimeout = 1000;
public:
    PollServer(const uint16_t& port = 8080, int nfds = defaultnfds, int timeout = defaulttimeout)
        : _port(port)
        , _nfds(nfds)
        , _timeout(timeout)
    {
        _listenSock = Sock::Socket();
        Sock::Bind(_listenSock, _port); // 绑定IP地址和端口号
        Sock::Listen(_listenSock);  // 将_listenSock设置为监听套接字
        logMessage(DEBUG, "Create ListenSock Success!");

        _fds = new struct pollfd[_nfds];
        for(int i = 0; i < _nfds; ++i)
        {
            _fds[i].fd = FD_NONE;
            _fds[i].events = _fds[i].revents = 0;
        }
        // 约定：监听套接字放在下标为0的位置
        // 关心监听套接字的读事件
        _fds[0].fd = _listenSock;
        _fds[0].events = POLLIN;
    }

    void Start()
    {
        while(true)
        {
            int n = poll(_fds, _nfds, _timeout);
            switch (n)
            {
            case -1:
                logMessage(WARNING, "Select Error %d %s", errno, strerror(errno));
                break;
            case 0:
                // 文件描述符的读事件未就绪,此时可以去处理其他事情
                logMessage(DEBUG, "Read Event Not Ready, Time Out"); 
                break;
            default:
                // 读事件就绪
                logMessage(DEBUG, "Read Event Ready");
                HandlerEvents();
                break;
            }
        }
    }

    ~PollServer()
    {
        if(_listenSock >= 0) close(_listenSock);
        if(_fds != nullptr) delete[] _fds; 
    }

private:
    void HandlerEvents()
    {
        for(int i = 0; i < _nfds; ++i)
        {
            // 跳过不合法的文件描述符
            if(_fds[i].fd == FD_NONE) continue;
            // 合法的文件描述符不一定就绪,在位图结构中的文件描述符才是就绪的
            if(_fds[i].revents & POLLIN)
            {
                // 1、读事件就绪,新连接到来,需要调用Accepter
                // 2、读事件就绪,数据到来,需要调用Recver
                if(_fds[i].fd == _listenSock)
                    Accepter(); 
                else
                    Recver(i);
            }
        }
    }

    void Accepter()
    {
        string clientIp;
        uint16_t clientPort = 0;
        // 此时获取新连接不会被阻塞住
        int sock = Sock::Accept(_listenSock, &clientIp, &clientPort);
        if(sock < 0)
        {
            logMessage(WARNING, "Accepter Error");
            return;
        }
        logMessage(NORMAL, "Accept A New Link Success: [%s:%d] fd:%d", clientIp.c_str(), clientPort, sock);
        
        // 找到一个合法的位置,将获取的新连接放到_fdArray数组中
        int pos = 1;
        for(; pos < _nfds; ++pos)
        {
            if(_fds[pos].fd == FD_NONE) break;
        }

        // poll所能监测的文件描述符是没有上限的,但是数组是有上限的
        // 当poll监测的文件描述符超过数组的上限时,可以进行扩容,也
        // 可以不进行扩容,取决于具体的应用场景。如果不进行扩容,直接
        // 将该连接关闭即可
        if(pos == _nfds)
        {
            logMessage(WARNING, "SelectServer Already Full, Close: %d", sock);
            close(sock);
        }
        else
        {
            _fds[pos].fd = sock;
            _fds[pos].events = POLLIN;
        }
    }

    void Recver(int pos)
    {
        // 此时调用read/recv等函数就不会被阻塞住了
        logMessage(NORMAL, "Data Arrives on %d File Descriptor", _fds[pos].fd);
        char buffer[1024];
        // 这里是有bug的,因为使用TCP协议进行通信,没有进行协议定制
        // 无法保证此次读取的数据就是一个完整的报文,而不是多个报文或半个报文
        int n = read(_fds[pos].fd, buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
            logMessage(NORMAL, "Client[%d]# %s", _fds[pos].fd, buffer);
        }
        else if(n == 0)
        {
            // 客户端关闭连接,服务端也要关闭连接
            logMessage(NORMAL, "Client[%d] Quit, Me Too", _fds[pos].fd);
            // 先关闭文件描述符,然后不再让select关心该文件描述符
            close(_fds[pos].fd);
            _fds[pos].fd = FD_NONE;
            _fds[pos].events = 0;
        }
        else
        {
            logMessage(WARNING, "%d File Descriptor Read Error %d:%s", _fds[pos].fd, errno, strerror(errno));
            // 先关闭文件描述符,然后不再让select关心该文件描述符
            close(_fds[pos].fd);
            _fds[pos].fd = FD_NONE;
            _fds[pos].events = 0;
        }
    }

private:
    uint16_t _port;
    int _listenSock;
    struct pollfd* _fds;
    int _nfds; // struct poll数组中元素的个数
    int _timeout;
};

#endif